package ga;

import java.io.File;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Date;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import ReadWGZ.ReadFilewgzMDR;

import convertTestsToJUnit.JUnitTestGenerator;

import crossover.Crossover;
import executor.ReflectionExecutor;
import  initialization.PopulationInitializer;
import instrumentation.BlockClass;
import instrumentation.TrackerCoverage;
import mdr.MethodDependences;
import mdr.RecommendGenerator;
import model.CUT;
import model.TestClusterBuilder;
import model.Filter;
import mutation.Mutation;
import mutation.MutationFactory;
import selection.Selection;
import immigration.Immigration;
import util.GAConfig;
import util.GAOperationsConfig;
import util.Log;
import util.Randomness;


public class GA {
	private static final int     TEST_SIZE_GOAL    = GAConfig.TestCaseSize;
	private static final int     POPULATION_SIZE       = GAConfig.PopulationSize;
	private static final int     elitismNum=(int)(((double)GAConfig.PopulationSize/(double)100) * GAConfig.elitismNumBest);
	private    TrackerCoverage tracker=  TrackerCoverage.getTracker();
	private  static final Mutation ChangeValueStrategy = GAOperationsConfig.getMutationChangeValueStrategy();
	private  static final Mutation MethodInsertStrategy = GAOperationsConfig.getMutationMethodInsertStrategy();
	private  static final Mutation MethodDeleteStrategy = GAOperationsConfig.getMutationMethodDeleteStrategy();
	private  static final Selection parentSelector = GAOperationsConfig.getParentSelector();
	private  static final Crossover crossoverStrategy = GAOperationsConfig.getCrossoverStrategy();
	private  static final Immigration immigrationStrategy = GAOperationsConfig.getImmigrationStrategy();
	private  static final PopulationInitializer populationInitializer = GAOperationsConfig.getPopulationInitializer();

	private  GAInfo     gaInfo     = new GAInfo();
	private RecommendGenerator recommendGenerator;
	private  long startTime;
	private  long elapsed;
	private static  long TestTimeout;
	public GA() {	
		ChangeValueStrategy.setExtraInfo(this.gaInfo);
		MethodInsertStrategy.setExtraInfo(this.gaInfo);	
		MethodDeleteStrategy.setExtraInfo(this.gaInfo);	
		 parentSelector.setExtraInfo(this.gaInfo);
		 immigrationStrategy.setExtraInfo(this.gaInfo);
		populationInitializer.setExtraInfo(this.gaInfo);

	}
	
	
	private void initialize(TestClusterBuilder tCluster) throws IOException, ClassNotFoundException{
		this.gaInfo.setTestCluster(tCluster);

			if (GAConfig.SaveData) 
				Log.log("\n"+tCluster.getClassToTest().keySet().toString());

			//the method recommender
			 if(GAConfig.UseReadWrietRelation){
				 System.out.println( "Building up method dependence model for diversifying...");
				 Set<Class<?>> cluster = new LinkedHashSet<Class<?>>();
					for(String cName:tCluster.getCluster().keySet()){
						Class<?> cls=tCluster.getCluster().get(cName).getClazz();
						if(Filter.isPrimitiveOrStringType(cls))continue;
						if(cls.getName().startsWith("java.")) continue;
						if(cls.getName().startsWith("sun.")) continue;
						if(cls.getName().contains("$")) continue;
						if(!Filter.canUse(cls))continue;
						cluster.add(cls);
					}

					MethodDependences recommender = new MethodDependences(cluster);
					 recommender.buildDependence(tCluster.getStatements());
					 System.out.println("Finishing method dependence model building");
					 this.gaInfo.setMethodRelations(recommender);
					 
					 ////print out the relation methods dependency
					Set<Class<?>> classToTest= new LinkedHashSet<Class<?>>();

					for(Class<?> cls: tCluster.getClassToTest().values())
							classToTest.add(cls);
					System.out.println("--------------------");
					System.out.println("     CUT: "+ classToTest);
					System.out.println("--------------------");
//					System.out.println(recommender.showDependence(classToTest));
//					System.out.println(recommender.showStatmentsWriteFileds(classToTest));
					 recommendGenerator=new  RecommendGenerator(gaInfo);
					 this.gaInfo.setRecommendGenerator(recommendGenerator);
					// System.out.println(recommendGenerator.toString());
			 }


	
		Individual[] population = populationInitializer.createPopulation();
		this.gaInfo.setPopulation(population);
		
		this.gaInfo.setGeneration(1);
		this.gaInfo.setPopulationFitness(0);
		String clzToTest=this.gaInfo.getClzToTest().getName();
		List<Integer> branchs = new ArrayList<Integer>( this.gaInfo.getClazzBranches(clzToTest));  
		maximumBranchNumber=branchs.size();
		 System.out.println("Total target branchs: "+ branchs);
		 if (GAConfig.SaveData) 
			 Log.log("Total target branchs: "+branchs.size()+ " "+ branchs);
	}
	
	private void execute() throws IOException, ClassNotFoundException{
		int noMinutes=1;
		String clzToTest=gaInfo.getClzToTest().getName();
		List<Integer> branchs = new ArrayList<Integer>( gaInfo.getClazzBranches(clzToTest));  

		for ( int it = 1; (it<=100000 && elapsed < TestTimeout) ; it++) {
			Individual[] population= this.gaInfo.getPopulation();
			//Calculate fitness of initial population
			int i=elitismNum;
			if(this.gaInfo.getGeneration()==1)
				i=0;
			
			for(;i<population.length;i++) {
				Individual individual= population[i];
				ReflectionExecutor.execute(this.gaInfo,individual);
				float []individualFitness= tracker.calculateFitness(individual,gaInfo.getClazzBranches(clzToTest));
				individual.setFitness(individualFitness[0]);
				individual.setBranchDistance(individualFitness[1]);
				
				tracker.reset();			
			}
	//		List<Integer> branchs = new ArrayList<Integer>( branchs);  
			float totalCoveredBranches=tracker.getTotalCoveredBranch(branchs);
			this.gaInfo.setTracker(tracker);
				

		//	 System.out.print("elapsed time: "+ elapsed + " of "+TestTimeout);
	//		System.out.println("\t Total Branch Covered= ( "+this.gaInfo.getPopulationFitness()+ " , "+ totalCoveredBranches +" )");
	//		System.out.println("\t Branch Covered= "+tracker.displayCoveredBranchs( branchs));
 
			//
			 if(this.gaInfo.getPopulationFitness()<totalCoveredBranches){
				 	this.gaInfo.setPopulationFitness(totalCoveredBranches);
				 	this.gaInfo.setNoImprovementCount(0);
			 }
			 else
				 this.gaInfo.setNoImprovementCount(this.gaInfo.getNoImprovementCount() + 1);
			
		   // perform the HillClimbing

				if(totalCoveredBranches>=maximumBranchNumber)
					break;
				//Restoring 
				if(GAConfig.Use_Immigration){
					 System.out.println(tracker.displayCoveredBranchs(branchs));
					Arrays.sort(this.gaInfo.getPopulation());
					Set<Individual> immigrants=immigrationStrategy.generateImmigrant();
					//System.out.println(immigrants);
					int lastIndx=1;
					for(Individual immigrantIndividual:immigrants){
						ReflectionExecutor.execute(this.gaInfo,immigrantIndividual);
						float []individualFitness= tracker.calculateFitness(immigrantIndividual,gaInfo.getClazzBranches(clzToTest));
						immigrantIndividual.setFitness(individualFitness[0]);
						immigrantIndividual.setBranchDistance(individualFitness[1]);						
						tracker.reset();	
						// replace the worst individuals with the generated immigrants
						population[population.length-lastIndx++]=immigrantIndividual;
					}
						
				}
			// Generations ...
			this.evolve();
			
			elapsed = (Calendar.getInstance().getTimeInMillis() - startTime);
	         if((this.gaInfo.getGeneration()%10)==0 && this.gaInfo.getGeneration()!=0 ){
	              System.out.print("elapsed time: "+ elapsed + " of "+TestTimeout);
	              System.out.println("\t Total Branch Covered= ( "+ totalCoveredBranches   +" ) and took "+ this.gaInfo.getGeneration() +" iterations");
	          }

			if((elapsed/60000)==noMinutes){
				 if (GAConfig.SaveData) Log.log("at " +  noMinutes +"  minute(s) Total Branch Covered= " + totalCoveredBranches );
	  				System.out.println("At " +  noMinutes +" minute(s) Total Branch Covered= " + totalCoveredBranches );

				 noMinutes++;

			}

		}
	}
	private void evolve(){
		// Generations ...

		// perform the improvement
		
		// Create the offSprings
		Individual[] offspringPopulation= new Individual[POPULATION_SIZE];
		 for (int j=elitismNum; j < (POPULATION_SIZE)&&(elapsed < TestTimeout) ;) {
			  //Select two parents for reproduction
			 Individual parent1 = parentSelector.selectTwoParents();
			 Individual  parent2 = parentSelector.selectTwoParents();
			//Get the two children
			Individual[] offspring = crossoverStrategy.crossOver(parent1, parent2);
			//Mutate
			Mutation mutationOperator= MutationFactory.getMutationOperator();
			offspring[0]=mutationOperator.mutateIndividual(offspring[0]);
			mutationOperator= MutationFactory.getMutationOperator();
			offspring[1]=mutationOperator.mutateIndividual(offspring[1]);
			offspringPopulation[j++]=offspring[0];
            if(j<POPULATION_SIZE)
            		offspringPopulation[j++]=offspring[1];		
			elapsed = (Calendar.getInstance().getTimeInMillis() - startTime);

		 }
		//elitis
		 Arrays.sort(this.gaInfo.getPopulation());
	       for(int i=0; i<elitismNum&& (elapsed < TestTimeout);i++){
	    	   offspringPopulation[i]= this.gaInfo.getPopulation()[i];//.clone();
	    	   elapsed = (Calendar.getInstance().getTimeInMillis() - startTime);
	       }
		 this.gaInfo.setPopulation(offspringPopulation);
		 this.gaInfo.setGeneration(this.gaInfo.getGeneration() + 1);

	}
	
	
	
	public void start(TestClusterBuilder tCluster, int runNO) throws IOException, ClassNotFoundException{

		startTime = Calendar.getInstance().getTimeInMillis();
		elapsed = Calendar.getInstance().getTimeInMillis() - startTime;
		gaInfo.setTestTimeOut(TestTimeout);
		gaInfo.setStartTime(startTime);
		String clzToTest=gaInfo.getClzToTest().getName();
		List<Integer> branchs = new ArrayList<Integer>( gaInfo.getClazzBranches(clzToTest));  


		this.initialize(tCluster);
		System.out.println("------------------");
		
		this.execute();

		 System.out.println ("\nDone testing at generation: " + gaInfo.getGeneration());
		 System.out.println("Maximum test time exceeded at " + elapsed );	
		 System.out.println(tracker.displayCoveredBranchs(branchs));
		 

		 if (GAConfig.SaveData) Log.log ("Done testing at generation: " + gaInfo.getGeneration());
		 if (GAConfig.SaveData) Log.log("Maximum test time exceeded at " + elapsed +" ms");
		 if (GAConfig.SaveData)	Log.log(tracker.displayCoveredBranchs(branchs));

		tracker.summarizeCoverage(branchs);
		/* convert tests to jUnit */
		String algorithm="GA";
		if(GAConfig.UseReadWrietRelation)
			algorithm="GAMDR";
			JUnitTestGenerator gen = new JUnitTestGenerator(GAConfig.getDirGeneratedTests(),gaInfo,algorithm,runNO);
		gen.writeSuite();


	}
	public static long randomseed;// = (int) GAConfig.RANDOM_SEED;
	static int maximumBranchNumber=Integer.MAX_VALUE;

	private void setTestTimeout(int i){
		if(i!=0)
		 TestTimeout = 60 * 1000*i;
		else
			 TestTimeout = 60 * 500;	 
	}
	public static void writeLogFile(String cut,int i ){
		String logFilePath = null;
		String fileName = new SimpleDateFormat("MM_dd_yyyy_hhmm'.txt'")
			.format(new Date());
		
	String cName = GAConfig.classFilePath.substring(0,GAConfig.classFilePath.lastIndexOf("/") );
	cName = cName.substring(cName.lastIndexOf("/") +1);
	//cName=GAConfig.classFilePath;
	String algorithm="_GA";
	if(GAConfig.UseReadWrietRelation){
		if(GAConfig.TargetHitBranches)
			algorithm="_GAMDR_HITBR";
		else
			algorithm="_GAMDR_NOCOVERED";

	}

	if(GAConfig.Seeding)
				logFilePath = "./logFiles/"+ cut+ algorithm+ "_Seeding_"+i+ "_PopNo_" + GAConfig.PopulationSize+ "_"+ fileName;
	else
		logFilePath = "./logFiles/"+ cut+ algorithm+"_NoSeeding_"+i+ "_PopNo_" + GAConfig.PopulationSize+ "_"+ fileName;

		
	if (logFilePath != null) {
		Log.logConfig(logFilePath);
//		experiment_configure_options(TestTimeout);
		System.out.println("Log file place: "+ new File(logFilePath).getAbsolutePath());
		}
		Log.log("       SUT               : "+ GAConfig.classFilePath);
		Log.log ("      Population Size   : "+GAConfig.PopulationSize);
		Log.log ("      TestCase Size     : "+TEST_SIZE_GOAL);
		Log.log ("      Elitism       : "+GAConfig.elitismNumBest);
		Log.log ("      Timeout           : "+TestTimeout/60000 + " minutes  " + TestTimeout+" ms");
		Log.log ("");

	}
	private  void experiment_configure_options(long TestTimeout,int j) {
		Log.log("Start testing #..."+j);
		Log.log ("      Seed              : "+randomseed);
	}

	public static void main(String[] args) throws IOException,
			ClassNotFoundException {

		Set<Class<?>> allClasses = new LinkedHashSet<Class<?>>();
		// get class from file
		allClasses.addAll(TestClusterBuilder.readClassFromFile());

		System.out.println("There are " + allClasses.size()
				+ " classes as input");

		System.out.println(allClasses);
		List<TestClusterBuilder> TestClusterList = TestClusterBuilder
				.buildTestClusterList(allClasses);
		ReadFilewgzMDR readFileWGZ = new ReadFilewgzMDR();

		for (int i =5; i <= 5; i += 6) {
			for(int times=1 ; times<=1;times++){
			GA algorithm = new GA();
			 Map<String, BlockClass> blockClasses=readFileWGZ.readFileWGZ(TestClusterList);
			 Map<String,Set<Integer>> classBranches=readFileWGZ.getClazzCondition(blockClasses);
		//	readFileWGZ.displayBlock(blockClasses);
			algorithm.gaInfo.setMapBlockClasses(blockClasses);
			algorithm.gaInfo.setClazzBranches(classBranches);

			algorithm.setTestTimeout(i);

			// set the random generation seeds

			for (TestClusterBuilder tCluster : TestClusterList) {

				Map<String, Class<?>> classesToTest = tCluster.getClassToTest();
				CUT cut = tCluster.getCut();
				if(cut==null) continue;
				Class<?> clz = classesToTest.values().iterator().next();
				System.out.println("Class " + clz.getName() + " is being Tested");
				algorithm.gaInfo.setClzToTest(clz);

				// if (GAConfig.SaveData)
				// Log.log("Class " + clz + " is being Tested");
				// System.out.println(classesToTest.keySet().toString());
				if (cut.getMethods().length == 0) {
					System.out.println("\tThere are no methods to test!");
					if (GAConfig.SaveData) {
						writeLogFile(clz.getName(), i);
						Log.log("\tThere are no methods to test!\n");
						Log.log.close();
					}

					continue;
				}
				if (GAConfig.SaveData)
					writeLogFile(clz.getName(), i);
				if (GAConfig.SaveData)
					Log.log("Class " + clz + " is being Tested");

				for (int run = 1; run <=30; run += 1) {
					System.out.println("Genetic Algorithm Start testing #..."
							+ run + " CUT is " + clz);

					randomseed = System.currentTimeMillis();//(long)1413256783116L;//
					Randomness.reset(randomseed);

					if (GAConfig.SaveData)
						algorithm.experiment_configure_options(TestTimeout, run);

					algorithm.start(tCluster,run);
					algorithm.tracker.setTrackerToNull();
					algorithm = new GA();
					algorithm.gaInfo.setMapBlockClasses(blockClasses);
					algorithm.gaInfo.setClazzBranches(classBranches);
					algorithm.gaInfo.setClzToTest(cut.getClazz());


				}
				if (GAConfig.SaveData)
					Log.log.close();

			}
		}
		}
		System.exit(0);
}
}